(function(stylesheet, stencils) {
	// Callbacks:
	// mxClientOnLoad is called  after the script is loaded with the stylesheet and the function to create a
	//     graph (wich takes the container as an argument and returns the graph instance that was created).
	// mxClientOnCreate is called when a graph has been created with the graph.
	mxStencilRegistry.dynamicLoading = false;

	// Adds CSS for tooltips
	try {
		var style = document.createElement('style')
		style.type = 'text/css'
		style.innerHTML = ['div.mxTooltip {',
			'-webkit-box-shadow: 3px 3px 12px #C0C0C0;',
			'-moz-box-shadow: 3px 3px 12px #C0C0C0;',
			'box-shadow: 3px 3px 12px #C0C0C0;',
			'background: #FFFFCC;',
			'border-style: solid;',
			'border-width: 1px;',
			'border-color: black;',
			'font-family: Arial;',
			'font-size: 8pt;',
			'position: absolute;',
			'cursor: default;',
			'padding: 4px;',
			'color: black;}'].join('\n');
		document.getElementsByTagName('head')[0].appendChild(style)
	} catch (e) {
		// ignore
	}

	var originalNoFo = mxClient.NO_FO;
	var mathJaxLoading = (typeof (MathJax) !== 'undefined' && typeof (MathJax.Hub) !== 'undefined');
	var mathJaxQueue = [];

	function loadMathJax() {
		// Uses existing configuration if MathJax already in page
		if (!mathJaxLoading) {
			mathJaxLoading = true;

			window.MathJax =
				{
					skipStartupTypeset: true,
					showMathMenu: false,
					messageStyle: 'none',
					AuthorInit: function () {
						MathJax.Hub.Config({
							jax: ['input/TeX', 'input/MathML', 'input/AsciiMath', 'output/SVG'],
							extensions: ['tex2jax.js', 'mml2jax.js', 'asciimath2jax.js'],
							TeX: {
								extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
							}
						});

						MathJax.Hub.Register.StartupHook('Begin', function () {
							for (var i = 0; i < mathJaxQueue.length; i++) {
								MathJax.Hub.Queue(['Typeset', MathJax.Hub, mathJaxQueue[i]]);
							}
						});
					}
				};

			var script = document.createElement('script');
			script.type = 'text/javascript';
			script.src = 'https://app.diagrams.net/math/MathJax.js';
			document.getElementsByTagName('head')[0].appendChild(script);
		}
	};

	function addMathJaxGraph(graph) {
		// Initial rendering when MathJax finished loading
		if (typeof (MathJax) !== 'undefined' && typeof (MathJax.Hub) !== 'undefined') {
			MathJax.Hub.Queue(['Typeset', MathJax.Hub, graph.container]);
		} else {
			mathJaxQueue.push(graph.container);
		}

		// Rendering math again on repaint
		graph.addListener(mxEvent.SIZE, function (sender, evt) {
			if (typeof (MathJax) !== 'undefined' && typeof (MathJax.Hub) !== 'undefined') {
				MathJax.Hub.Queue(['Typeset', MathJax.Hub, graph.container]);
			}
		});
	};

	// Handles relative images
	mxGraph.prototype.getImageFromBundles = function (key) {
		if (key != null) {
			if (key.substring(0, 7) != 'http://' && key.substring(0, 8) != 'https://' && key.substring(0, 10) != 'data:image') {
				if (key.charAt(0) == '/') {
					key = key.substring(1, key.length);
				}

				key = 'https://app.diagrams.net/' + key;
			}

			return key;
		}

		return null;
	};

	if (stencils != null) {
		for (var i = 0; i < stencils.length; i++) {
			var xmlDoc = mxUtils.parseXml(stencils[i]);
			mxStencilRegistry.parseStencilSet(xmlDoc.documentElement);
		}
	}

	// Panning for touch devices
	if (mxClient.IS_TOUCH) {
		mxPanningHandler.prototype.isPanningTrigger = function (me) {
			return true;
		};
	}

	(function () {
		function initGraph(container) {
			try {
				var child = container.firstChild;

				while (child != null && child.nodeType != mxConstants.NODETYPE_ELEMENT) {
					child = child.nextSibling;
				}

				var xml = mxUtils.trim(child.innerHTML);
				container.innerHTML = '';

				// Instance needed for decompress helper function
				var graph = new Graph(container);

				if (xml.substring(0, 4) == '&lt;') {
					xml = xml.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;gt;/g, '&gt;').replace(/&amp;lt;/g, '&lt;').replace(/&amp;quot;/g, '&quot;').replace(/&#xa;/g, '\n');
				} else if (xml.substring(0, 3) == '%3C') {
					xml = decodeURIComponent(xml);
				} else {
					xml = Graph.decompress(xml);
				}

				var xmlDocument = mxUtils.parseXml(xml);
				var configNode = null;
				var diagrams = null;

				if (xmlDocument.documentElement != null && xmlDocument.documentElement.nodeName == 'mxfile') {
					diagrams = xmlDocument.documentElement.getElementsByTagName('diagram');
					configNode = xmlDocument.documentElement;

					if (diagrams.length > 0) {
						xml = mxUtils.getTextContent(diagrams[0]);
						xml = Graph.decompress(xml);
						xmlDocument = mxUtils.parseXml(xml);
					}
				}

				if (xmlDocument.documentElement != null && xmlDocument.documentElement.nodeName == 'mxGraphModel') {
					var decoder = new mxCodec(xmlDocument);
					var node = xmlDocument.documentElement;

					if (configNode == null) {
						configNode = node;
					}

					graph.resetViewOnRootChange = false;
					graph.setEnabled(false);

					if (diagrams != null && diagrams.length > 0) {
						/**
						 * Adds placeholder for %page% and %pagenumber%
						 */
						var graphGetGlobalVariable = graph.getGlobalVariable;

						graph.getGlobalVariable = function (name) {
							if (name == 'page') {
								return diagrams[0].getAttribute('name') || 'Page-1';
							} else if (name == 'pagenumber') {
								return 1;
							} else if (name == 'pagecount') {
								return diagrams.length;
							}

							return graphGetGlobalVariable.apply(this, arguments);
						};
					}

					graph.foldingEnabled = configNode.getAttribute('nav') == '1';
					graph.cellRenderer.forceControlClickHandler = graph.foldingEnabled;

					var tooltips = configNode.getAttribute('tooltips');

					if (tooltips != '0') {
						graph.setTooltips(true);
					} else {
						graph.setTooltips(false);
					}

					// Loads the stylesheet
					if (stylesheet != null) {
						var xmlDoc = mxUtils.parseXml(stylesheet);
						var dec = new mxCodec(xmlDoc);
						dec.decode(xmlDoc.documentElement, graph.getStylesheet());
					}

					var math = configNode.getAttribute('math');

					if (math == '1') {
						mxClient.NO_FO = true;
						loadMathJax();
					}

					// Enables panning with left mouse button
					var pan = configNode.getAttribute('pan');

					if (pan != '0') {
						graph.panningHandler.useLeftButtonForPanning = true;
						graph.panningHandler.ignoreCell = true;
						container.style.cursor = 'move';
						graph.setPanning(true);
					} else {
						container.style.cursor = 'default';
					}

					var resize = configNode.getAttribute('resize');
					var border = Number(configNode.getAttribute('border') || 0);
					graph.border = border;

					var fit = configNode.getAttribute('fit');

					if ((container.style.width != '100%' && fit != '1' && resize != '0') ||
						(container.style.width == '' && container.style.height == '')) {
						graph.resizeContainer = true;
						graph.centerZoom = false;
					} else {
						// Updates the container height for autosize width
						if (resize != '0' && container.style.width == '100%' && container.style.height == '') {
							graph.resizeContainer = true;
							graph.centerZoom = false;

							graph.doResizeContainer = function (width, height) {
								// Fixes container size for different box models
								if (mxClient.IS_IE) {
									if (document.documentMode >= 9) {
										width += 3;
										height += 5;
									} else {
										width += 1;
										height += 1;
									}
								} else {
									height += 1;
								}

								if (this.maximumContainerSize != null) {
									width = Math.min(this.maximumContainerSize.width, width);
									height = Math.min(this.maximumContainerSize.height, height);
								}

								this.container.style.height = Math.ceil(height + 18) + 'px';
							};
						} else {
							graph.centerZoom = true;
						}
					}

					// Adds handling for hyperlinks, tooltips
					var links = configNode.getAttribute('links');
					var hl = configNode.getAttribute('highlight');

					if (links != '0' || tooltips != '0') {
						var cursor = container.style.cursor;
						var tol = graph.getTolerance();

						graph.addMouseListener(
							{
								currentState: null,
								currentLink: null,
								highlight: (hl != null && hl != '' && hl != mxConstants.NONE) ?
									new mxCellHighlight(graph, hl, 2) : null,
								startX: 0,
								startY: 0,
								mouseDown: function (sender, me) {
									this.startX = me.getGraphX();
									this.startY = me.getGraphY();
								},
								mouseMove: function (sender, me) {
									if (graph.isMouseDown) {
										if (this.currentLink != null) {
											var dx = Math.abs(this.startX - me.getGraphX());
											var dy = Math.abs(this.startY - me.getGraphY());

											if (dx > tol || dy > tol) {
												this.clear();
											}
										}
									} else {
										if (this.currentState != null && (me.getState() == this.currentState || me.getState() == null) &&
											graph.intersects(this.currentState, me.getGraphX(), me.getGraphY())) {
											return;
										}

										var tmp = graph.view.getState(me.getCell());

										if (tmp != this.currentState) {
											if (this.currentState != null) {
												this.clear();
											}

											this.currentState = tmp;

											if (this.currentState != null) {
												this.activate(this.currentState);
											}
										}
									}
								},
								mouseUp: function (sender, me) {
									var tmp = this.currentLink;
									this.clear();

									if (tmp != null) {
										if (tmp.charAt(0) == '#') {
											window.location.hash = tmp;
										} else {
											window.open(tmp);
										}
									}
								},
								activate: function (state) {
									this.currentLink = graph.getLinkForCell(state.cell);

									if (this.currentLink != null) {
										container.style.cursor = 'pointer';

										if (this.highlight != null) {
											this.highlight.highlight(state);
										}
									}
								},
								clear: function () {
									container.style.cursor = cursor;
									this.currentState = null;
									this.currentLink = null;

									if (this.highlight != null) {
										this.highlight.hide();
									}
								}
							});
					}

					var x0 = Number(configNode.getAttribute('x0') || 0);
					var y0 = Number(configNode.getAttribute('y0') || 0);
					graph.view.translate.x = -x0 + border;
					graph.view.translate.y = -y0 + border;

					function graphAdded(node) {
						var img = node.getAttribute('backgroundImage');

						if (img != null) {
							img = JSON.parse(img);
							graph.setBackgroundImage(new mxImage(img.src, img.width, img.height));
							graph.view.validateBackgroundImage();
						}

						if (fit != '0') {
							graph.fit(border);
						}

						if (math == '1') {
							addMathJaxGraph(graph);
						}

						// Keeps hashtag links on same page
						var links = graph.container.getElementsByTagName('a');

						if (links != null) {
							for (var i = 0; i < links.length; i++) {
								var href = links[i].getAttribute('href');

								if (href != null && href.charAt(0) == '#' &&
									links[i].getAttribute('target') == '_blank') {
									links[i].removeAttribute('target');
								}
							}
						}
					};

					// Load from URL via url attribute
					var url = configNode.getAttribute('url');

					if (url != null) {
						try {
							// Workaround for unsupported CORS in IE9 XHR
							var xhr = (navigator.userAgent != null && navigator.userAgent.indexOf('MSIE 9') > 0) ?
								new XDomainRequest() : new XMLHttpRequest();
							xhr.open('GET', url);

							xhr.onload = mxUtils.bind(this, function () {
								try {
									if (math == '1') {
										mxClient.NO_FO = mxClient.IS_SF;
									}

									var data = (xhr.getText != null) ? xhr.getText() : xhr.responseText;

									if (data != null) {
										var newDocument = mxUtils.parseXml(data);

										// LATER: Add support for .png (with XML) files
										// Adds support for HTML
										if (newDocument != null && newDocument.documentElement.nodeName == 'html') {
											var divs = newDocument.documentElement.getElementsByTagName('div');

											if (divs.length > 0 && divs[0].getAttribute('class') == 'mxgraph') {
												var divs2 = divs[0].getElementsByTagName('div');

												if (divs2.length > 0) {
													var data = mxUtils.getTextContent(divs2[0]);
													data = Graph.decompress(data);

													if (data.length > 0) {
														newDocument = mxUtils.parseXml(data);
													}
												}
											}
										}

										if (newDocument != null && newDocument.documentElement.nodeName == 'svg') {
											var tmp = newDocument.documentElement.getAttribute('content');

											if (tmp != null && tmp.charAt(0) != '<' && tmp.charAt(0) != '%') {
												tmp = unescape((window.atob) ? atob(tmp) : Base64.decode(cont, tmp));
											}

											if (tmp != null && tmp.charAt(0) == '%') {
												tmp = decodeURIComponent(tmp);
											}

											if (tmp != null && tmp.length > 0) {
												newDocument = mxUtils.parseXml(tmp);
											}
										}

										if (newDocument.documentElement.nodeName == 'mxfile') {
											var diagrams = newDocument.documentElement.getElementsByTagName('diagram');

											if (diagrams.length > 0) {
												var text = mxUtils.trim(mxUtils.getTextContent(diagrams[0]));
												var node = null;

												if (text.length > 0) {
													var tmp = Graph.decompress(text);

													if (tmp != null && tmp.length > 0) {
														newDocument = mxUtils.parseXml(tmp);
													}
												} else {
													var temp = mxUtils.getChildNodes(diagrams[0]);

													if (temp.length > 0) {
														// Creates new document for unique IDs within mxGraphModel
														newDocument = mxUtils.createXmlDocument();
														newDocument.appendChild(newDocument.importNode(temp[0], true));
													}
												}
											}
										}

										decoder = new mxCodec(newDocument);
										decoder.decode(newDocument.documentElement, graph.getModel());
										graphAdded(newDocument.documentElement);
									} else {
										graph.container.innerHTML = 'Cannot load ' + mxUtils.htmlEntities(url);
									}

									mxClient.NO_FO = originalNoFo;
								} catch (e) {
									graph.container.innerHTML = 'Cannot load ' + mxUtils.htmlEntities(url) + ': ' + mxUtils.htmlEntities(e.message);
								}
							});

							xhr.onerror = function () {
								graph.container.innerHTML = 'Cannot load ' + mxUtils.htmlEntities(url);
							};

							xhr.send();
						} catch (e) {
							graph.container.innerHTML = 'Cannot load ' + mxUtils.htmlEntities(url) + ': ' + mxUtils.htmlEntities(e.message);
						}
					} else {
						decoder.decode(node, graph.getModel());
						graphAdded(node);
					}

					if (container.style.width != '100%' && fit != '0' && resize == '1') {
						graph.resizeContainer = true;
						graph.centerZoom = false;
					}

					// Adds zoom, edit etc in top, left corner
					var buttons = document.createElement('div');
					buttons.style.position = 'absolute';
					buttons.style.overflow = 'visible';
					buttons.style.cursor = 'pointer';

					var bs = graph.getBorderSizes();

					var left = 0;
					var fontSize = 10;
					var bw = 16;
					var bh = 16;

					if (mxClient.IS_TOUCH) {
						bw = 24;
						bh = 24;
						var fontSize = 14;
					}

					function addButton(label, funct) {
						var btn = document.createElement('div');
						btn.style.position = 'absolute';
						btn.style.border = '1px solid gray';
						btn.style.textAlign = 'center';
						btn.style.cursor = 'hand';
						btn.style.width = bw + 'px';
						btn.style.height = bh + 'px';
						btn.style.left = left + 'px';
						btn.style.top = '0px';
						btn.style.backgroundColor = 'white';
						mxUtils.setOpacity(btn, 50);

						var table = document.createElement('table');
						table.style.borderWidth = '0px';
						table.style.width = '100%';
						table.style.height = '100%';
						var tbody = document.createElement('tbody');
						var tr = document.createElement('tr');
						var td = document.createElement('td');
						td.style.verticalAlign = 'middle';
						td.style.textAlign = 'center';
						td.style.fontSize = fontSize + 'px';
						td.style.padding = '0px';
						mxUtils.write(td, label);
						tr.appendChild(td);
						tbody.appendChild(tr);
						table.appendChild(tbody);
						btn.appendChild(table);

						mxEvent.addListener(btn, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', function (evt) {
							mxEvent.consume(evt);
						});

						mxEvent.addListener(btn, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', function (evt) {
							funct();
							mxEvent.consume(evt);
						});

						if (!mxClient.IS_POINTER && mxClient.IS_TOUCH) {
							mxEvent.addListener(btn, 'touchstart', function (evt) {
								mxEvent.consume(evt);
							});

							mxEvent.addListener(btn, 'touchend', function (evt) {
								funct();
								mxEvent.consume(evt);
							});
						}

						left += bw;
						buttons.appendChild(btn);

						return btn;
					};

					var zoom = configNode.getAttribute('zoom');

					if (zoom != '0') {
						addButton('+', function () {
							graph.zoomIn();
						});

						addButton('-', function () {
							graph.zoomOut();
						});
					}

					var edit = configNode.getAttribute('edit');

					if (edit != null) {
						var button = addButton('', function () {
							// _blank is a special value to open a new editor
							// in client mode and send the XML as a message
							if (edit == '_blank') {
								if (url != null) {
									window.open('https://app.diagrams.net/#U' + encodeURIComponent(url));
								} else {
									var wnd = null;

									var receive = function (evt) {
										if (evt.data == 'ready' && evt.source == wnd) {
											wnd.postMessage(xml, '*');
											window.removeEventListener('message', receive);
										}
									};

									window.addEventListener('message', receive);
									wnd = window.open('https://app.diagrams.net/?client=1');
								}
							} else {
								window.open(edit);
							}
						});

						// Do not use HTML entity to avoid problems with XHTML
						button.innerHTML = '...';
					}

					function show() {
						buttons.style.top = (container.offsetTop + bs.y) + 'px';
						buttons.style.left = (container.offsetLeft + bs.x) + 'px';
						buttons.style.visibility = 'visible';
					};

					if (!mxClient.IS_POINTER && !mxClient.IS_TOUCH) {
						function hide() {
							buttons.style.visibility = 'hidden';
						};

						mxEvent.addListener(container, 'mouseover', show);
						mxEvent.addListener(buttons, 'mouseover', show);
						mxEvent.addListener(container, 'mouseout', hide);
						mxEvent.addListener(buttons, 'mouseout', hide);
						hide();
					} else {
						show();
					}

					if (buttons.firstChild != null) {
						if (container.nextSibling != null) {
							container.parentNode.insertBefore(buttons, container.nextSibling);
						} else {
							container.parentNode.appendChild(buttons);
						}
					}

					if (typeof (window.mxClientOnCreate) == 'function') {
						window.mxClientOnCreate(graph);
					}
				}
			} catch (err) {
				if (window.console != null) {
					console.log('Error:', err);
				}
			}

			mxClient.NO_FO = originalNoFo;

			return graph;
		};

		if (typeof (mxClientOnLoad) == 'function') {
			mxClientOnLoad(stylesheet, initGraph);
		} else if (mxClient.isBrowserSupported()) {
			var tmp = document.getElementsByTagName('*');
			var divs = [];

			for (var i = 0; i < tmp.length; i++) {
				divs.push(tmp[i]);
			}

			for (var i = 0; i < divs.length; i++) {
				if (divs[i].className.toString().indexOf('mxgraph') >= 0) {
					initGraph(divs[i]);
				}
			}
		}
	})();
});
// Last line will be replaced by servlet for passing arguments.

